//
// FileChannel.cpp
//
// $Id: //poco/1.4/Foundation/src/FileChannel.cpp#3 $
//
// Library: Foundation
// Package: Logging
// Module:  FileChannel
//
// Copyright (c) 2004-2006, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier:	BSL-1.0
//


#include "Poco/FileChannel.h"
#include "Poco/LocalDateTime.h"
#include "Poco/String.h"
#include "Poco/Timespan.h"
#include "Poco/Exception.h"
#include "Poco/Platform.h"

#include <cctype>
#include <iostream>
#include <string>

#if defined(POCO_OS_FAMILY_UNIX)
    #include "FileChannel_Linux.cpp"
#endif
namespace Poco {
const std::string FileChannel::PROP_PATH         = "path";
const std::string FileChannel::PROP_ROTATION     = "rotation";
const std::string FileChannel::PROP_ARCHIVE      = "archive";
const std::string FileChannel::PROP_TIMES        = "times";
const std::string FileChannel::PROP_COMPRESS     = "compress";
const std::string FileChannel::PROP_PURGEAGE     = "purgeAge";
const std::string FileChannel::PROP_PURGECOUNT   = "purgeCount";
const std::string FileChannel::PROP_FLUSH        = "flush";
const std::string FileChannel::PROP_ROTATEONOPEN = "rotateOnOpen";

FileChannel::FileChannel(const std::string& path,const string& prefixOption):
	m_times("utc"),
	m_compress(false),
	m_flush(true),
	m_rotateOnOpen(false),
	m_pFile(0),
    m_pRotateStrategy(0),
	m_pArchiveStrategy(new ArchiveByTimestampStrategy<LocalDateTime>()),
	m_pPurgeStrategy(0),
	m_prefixOption(prefixOption),
	m_path(path)
{
    parseOption();
}


FileChannel::~FileChannel()
{
    close();
    if (m_pRotateStrategy)
        delete m_pRotateStrategy;
    if (m_pArchiveStrategy)
        delete m_pArchiveStrategy;
    if (m_pPurgeStrategy)
        delete m_pPurgeStrategy;
}


void FileChannel::open()
{
	if (m_pFile != NULL)
		return;
	FastMutex::ScopedLock lock(m_mutex);
	
	if (!m_pFile)
	{
		m_pFile = new std::fstream();
		m_pFile->open(m_path.c_str(),std::fstream::in | std::fstream::out | std::fstream::app);
	}
}

void FileChannel::log(const string& msg)
{
    open();
    FastMutex::ScopedLock lock(m_mutex);
	if (m_pRotateStrategy  && m_pArchiveStrategy && m_pRotateStrategy->mustRotate(this))
	{
		try
		{
			m_pArchiveStrategy->archive(m_path);
			purge();
			/// after archived, file need reopen
			delete m_pFile;
			m_pFile = new std::fstream();
			m_pFile->open(m_path.c_str(),std::fstream::in | std::fstream::out | std::fstream::app);		
		}
		catch (...)
		{
			delete m_pFile;
			m_pFile = new std::fstream();
			m_pFile->open(m_path.c_str(),std::fstream::in | std::fstream::out | std::fstream::app);	
		}
		// we must call mustRotate() again to give the
		// RotateByIntervalStrategy a chance to write its timestamp
		// to the new file.
		m_pRotateStrategy->mustRotate(this);
	}
    (*m_pFile)<<msg<<std::endl;
    if ( m_flush)
        m_pFile->flush();
}

void FileChannel::log(const string& msg,LogPriority prio)
{
    open();
    FastMutex::ScopedLock lock(m_mutex);
	if (m_pRotateStrategy  && m_pArchiveStrategy && m_pRotateStrategy->mustRotate(this))
	{
		try
		{
			m_pArchiveStrategy->archive(m_path);
			purge();
			/// after archived, file need reopen
			delete m_pFile;
			m_pFile = new std::fstream();
			m_pFile->open(m_path.c_str(),std::fstream::in | std::fstream::out | std::fstream::app);		
		}
		catch (...)
		{
			delete m_pFile;
			m_pFile = new std::fstream();
			m_pFile->open(m_path.c_str(),std::fstream::in | std::fstream::out | std::fstream::app);	
		}
		// we must call mustRotate() again to give the
		// RotateByIntervalStrategy a chance to write its timestamp
		// to the new file.
		m_pRotateStrategy->mustRotate(this);
	}
    (*m_pFile)<<getPrefix(prio)<<":"<<msg<<std::endl;
    if ( m_flush)
        m_pFile->flush();
}
        
void FileChannel::log(const string& msg,LogPriority prio,const char* file,int line)
{
	open();
	FastMutex::ScopedLock lock(m_mutex);
	if (m_pRotateStrategy  && m_pArchiveStrategy && m_pRotateStrategy->mustRotate(this))
	{
		try
		{
			m_pArchiveStrategy->archive(m_path);
			purge();
			/// after archived, file need reopen
			delete m_pFile;
			m_pFile = new std::fstream();
			m_pFile->open(m_path.c_str(),std::fstream::in | std::fstream::out | std::fstream::app);		
		}
		catch (...)
		{
			delete m_pFile;
			m_pFile = new std::fstream();
			m_pFile->open(m_path.c_str(),std::fstream::in | std::fstream::out | std::fstream::app);	
		}
		// we must call mustRotate() again to give the
		// RotateByIntervalStrategy a chance to write its timestamp
		// to the new file.
		m_pRotateStrategy->mustRotate(this);
	}
    (*m_pFile)<<getPrefix(prio)<<":"<<file<<":"<<line<<":"<<msg<<std::endl;
    if ( m_flush)
        m_pFile->flush();
}

void FileChannel::close()
{
	FastMutex::ScopedLock lock(m_mutex);
	if (m_pFile)
		m_pFile->close();
	
	delete m_pFile;
	m_pFile = 0;
}

void FileChannel::setProperty(const std::string& name, const std::string& value)
{
	FastMutex::ScopedLock lock(m_mutex);

	if (name == PROP_TIMES)
	{
		m_times = value;

		if (!m_rotation.empty())
			setRotation(m_rotation);

		if (!m_archive.empty())
			setArchive(m_archive);
	}
	else if (name == PROP_PATH)
	{
		m_path = value;
		delete m_pFile;
		m_pFile = new std::fstream();
		m_pFile->open(m_path.c_str(),std::fstream::in | std::fstream::out | std::fstream::app);		
	}
	else if (name == PROP_ROTATION)
		setRotation(value);
	else if (name == PROP_ARCHIVE)
		setArchive(value);
	else if (name == PROP_COMPRESS)
		setCompress(value);
	else if (name == PROP_PURGEAGE)
		setPurgeAge(value);
	else if (name == PROP_PURGECOUNT)
		setPurgeCount(value);
	else if (name == PROP_FLUSH)
		setFlush(value);
	else if (name == PROP_ROTATEONOPEN)
		setRotateOnOpen(value);
}


std::string FileChannel::getProperty(const std::string& name) const
{
	if (name == PROP_TIMES)
		return m_times;
	else if (name == PROP_PATH)
		return m_path;
	else if (name == PROP_ROTATION)
		return m_rotation;
	else if (name == PROP_ARCHIVE)
		return m_archive;
	else if (name == PROP_COMPRESS)
		return std::string(m_compress ? "true" : "false");
	else if (name == PROP_PURGEAGE)
		return m_purgeAge;
	else if (name == PROP_PURGECOUNT)
		return m_purgeCount;
	else if (name == PROP_FLUSH)
		return std::string(m_flush ? "true" : "false");
	else if (name == PROP_ROTATEONOPEN)
		return std::string(m_rotateOnOpen ? "true" : "false");
	
	return "";
}

void FileChannel::write(const std::string& text)
{
    *m_pFile << text;
    if (m_flush)
        *m_pFile << std::endl;
    else
        *m_pFile << "\n";
    if (!m_pFile->good()) throw WriteFileException(m_path);
}

void FileChannel::setFlush(const std::string& flush)
{
    m_flush = icompare(flush, "true") == 0;
}

inline void FileChannel::setPrefixOption(const string& prefixOption)
{
    m_prefixOption = prefixOption;
	m_options.clear();
    parseOption();
}


inline void FileChannel::parseOption()
{
    string::size_type pos = m_prefixOption.find("%");
    while( pos != string::npos)
    {
        if (pos == m_prefixOption.length() - 1)
            break;
        OptionPair tmp(m_prefixOption.substr(pos,2),pos);
        m_options.push_back(tmp);
        pos = m_prefixOption.find("%",pos+1);
    }
}


// 各个平台的log，在各个FileChannel_*.cpp中实现
void FileChannel::setRotation(const std::string& rotation)
{
	std::string::const_iterator it  = rotation.begin();
	std::string::const_iterator end = rotation.end();
	int n = 0;
	while (it != end && isspace(*it)) ++it;
	while (it != end && isdigit(*it)) { n *= 10; n += *it++ - '0'; }
	while (it != end && isspace(*it)) ++it;
	std::string unit;
	while (it != end && isalpha(*it)) unit += *it++;
	
	RotateStrategy* pStrategy = 0;
	if ((rotation.find(',') != std::string::npos) || (rotation.find(':') != std::string::npos))
	{
		if (m_times == "utc")
			pStrategy = new RotateAtTimeStrategy<DateTime>(rotation);
		else if (m_times == "local")
			pStrategy = new RotateAtTimeStrategy<LocalDateTime>(rotation);
		else
			throw PropertyNotSupportedException("times", m_times);
	}
	else if (unit == "daily")
		pStrategy = new RotateByIntervalStrategy(Timespan(1*Timespan::DAYS));
	else if (unit == "weekly")
		pStrategy = new RotateByIntervalStrategy(Timespan(7*Timespan::DAYS));
	else if (unit == "monthly")
		pStrategy = new RotateByIntervalStrategy(Timespan(30*Timespan::DAYS));
	else if (unit == "seconds") // for testing only
		pStrategy = new RotateByIntervalStrategy(Timespan(n*Timespan::SECONDS));
	else if (unit == "minutes")
		pStrategy = new RotateByIntervalStrategy(Timespan(n*Timespan::MINUTES));
	else if (unit == "hours")
		pStrategy = new RotateByIntervalStrategy(Timespan(n*Timespan::HOURS));
	else if (unit == "days")
		pStrategy = new RotateByIntervalStrategy(Timespan(n*Timespan::DAYS));
	else if (unit == "weeks")
		pStrategy = new RotateByIntervalStrategy(Timespan(n*7*Timespan::DAYS));
	else if (unit == "months")
		pStrategy = new RotateByIntervalStrategy(Timespan(n*30*Timespan::DAYS));
	else if (unit == "K")
		pStrategy = new RotateBySizeStrategy(n*1024);
	else if (unit == "M")
		pStrategy = new RotateBySizeStrategy(n*1024*1024);
	else if (unit.empty())
		pStrategy = new RotateBySizeStrategy(n);
	else if (unit != "never")
		throw InvalidArgumentException("rotation", rotation);
	delete m_pRotateStrategy;
	m_pRotateStrategy = pStrategy;
	m_rotation = rotation;
}


void FileChannel::setArchive(const std::string& archive)
{
	ArchiveStrategy* pStrategy = 0;
	if (archive == "number")
	{
		pStrategy = new ArchiveByNumberStrategy;
	}
	else if (archive == "timestamp")
	{
		if (m_times == "utc")
			pStrategy = new ArchiveByTimestampStrategy<DateTime>;
		else if (m_times == "local")
			pStrategy = new ArchiveByTimestampStrategy<LocalDateTime>;
		else
			throw PropertyNotSupportedException("times", m_times);
	}
	else throw InvalidArgumentException("archive", archive);
	delete m_pArchiveStrategy;
	m_pArchiveStrategy = pStrategy;
	m_archive = archive;
}


void FileChannel::setCompress(const std::string& compress)
{
	m_compress = icompare(compress, "true") == 0;
// 	if (m_pArchiveStrategy)
// 		m_pArchiveStrategy->compress(m_compress);
}


void FileChannel::setPurgeAge(const std::string& age)
{
	delete m_pPurgeStrategy;
	m_pPurgeStrategy = 0;
	m_purgeAge = "none";

	if (age.empty() || 0 == icompare(age, "none"))
		return;

	std::string::const_iterator it  = age.begin();
	std::string::const_iterator end = age.end();
	int n = 0;
	while (it != end && isspace(*it)) ++it;
	while (it != end && isdigit(*it)) { n *= 10; n += *it++ - '0'; }
	if (0 == n)
		throw InvalidArgumentException("Zero is not valid purge age.");
	
	while (it != end && isspace(*it)) ++it;

	std::string unit;
	while (it != end && isalpha(*it)) unit += *it++;
	
	Timespan::TimeDiff factor = Timespan::SECONDS;
	if (unit == "minutes")
		factor = Timespan::MINUTES;
	else if (unit == "hours")
		factor = Timespan::HOURS;
	else if (unit == "days")
		factor = Timespan::DAYS;
	else if (unit == "weeks")
		factor = 7*Timespan::DAYS;
	else if (unit == "months")
		factor = 30*Timespan::DAYS;
	else if (unit != "seconds")
		throw InvalidArgumentException("purgeAge", age);

	m_pPurgeStrategy = new PurgeByAgeStrategy(Timespan(factor*n));
	m_purgeAge = age;
}


void FileChannel::setPurgeCount(const std::string& count)
{
	delete m_pPurgeStrategy;
	m_pPurgeStrategy = 0;
	m_purgeAge = "none";

	if (count.empty() || 0 == icompare(count, "none"))
		return;

	std::string::const_iterator it  = count.begin();
	std::string::const_iterator end = count.end();
	int n = 0;
	while (it != end && isspace(*it)) ++it;
	while (it != end && isdigit(*it)) { n *= 10; n += *it++ - '0'; }
	if (0 == n)
		throw InvalidArgumentException("Zero is not valid purge count.");
	while (it != end && isspace(*it)) ++it;

	delete m_pPurgeStrategy;
	m_pPurgeStrategy = new PurgeByCountStrategy(n);
	m_purgeCount = count;
}

void FileChannel::setRotateOnOpen(const std::string& rotateOnOpen)
{
	m_rotateOnOpen = icompare(rotateOnOpen, "true") == 0;
}

void FileChannel::purge()
{
	if (m_pPurgeStrategy)
	{
		try
		{
			m_pPurgeStrategy->purge(m_path);
		}
		catch (...)
		{
		}
	}
}


} // namespace Poco
